#include "sequence.h"
#include <cstring>
#include "util.h"

using namespace std;
using namespace MidiFile;

//------------------------------------------------------------------------------
// Constructores y destructor
//------------------------------------------------------------------------------

Sequence::Sequence(const Sequence &other) throw (bad_alloc)
{
    isLoaded = other.isLoaded;
    format = other.format;
    divisionType = other.divisionType;

    if (divisionType == TicksPerBeat)
        ticksPerBeat = other.ticksPerBeat;
    else {
        framesPerSecond = other.framesPerSecond;
        ticksPerFrame = other.ticksPerFrame;
    }

    if (isLoaded) {
        tracks.resize(other.tracks.size());

        for (unsigned short i = 0; i < tracks.size(); i++)
            tracks[i] = new Track(*other.tracks[i]);
    }
}

Sequence::Sequence(const char *fileName) throw (FileException, bad_alloc)
{
    isLoaded = false;
    readFile(fileName);
}

Sequence::~Sequence() throw ()
{
    if (isLoaded)
        unloadTracks();
}

//------------------------------------------------------------------------------
// Propiedades
//------------------------------------------------------------------------------

MidiFormat Sequence::getFormat() const throw (NotLoadedException)
{
    if (!isLoaded)
        throw NotLoadedException();

    switch (format) {
    case 0:
        return SingleTrack;
    case 1:
        return MultipleSimultaneous;
    default:
        return MultipleIndependent;
    }
}

DivisionType Sequence::getDivisionType() const throw (NotLoadedException)
{
    if (!isLoaded)
        throw NotLoadedException();

    return divisionType;
}

unsigned short Sequence::getNumTracks() const throw (NotLoadedException)
{
    if (!isLoaded)
        throw NotLoadedException();

    return tracks.size();
}

unsigned short Sequence::getTicksPerBeat() const throw (NotLoadedException, InvalidException)
{
    if (!isLoaded)
        throw NotLoadedException();

    if (divisionType != TicksPerBeat)
        throw InvalidException();

    return ticksPerBeat;
}

unsigned char Sequence::getFramesPerSecond() const throw (NotLoadedException, InvalidException)
{
    if (!isLoaded)
        throw NotLoadedException();

    if (divisionType != FramesPerSecond)
        throw InvalidException();

    return framesPerSecond;
}

unsigned char Sequence::getTicksPerFrame() const throw (NotLoadedException, InvalidException)
{
    if (!isLoaded)
        throw NotLoadedException();

    if (divisionType != FramesPerSecond)
        throw InvalidException();

    return ticksPerFrame;
}

//------------------------------------------------------------------------------
// Metodos
//------------------------------------------------------------------------------

void Sequence::readFile(const char *filename) throw (FileException, bad_alloc)
{
    char header[4];
    unsigned int length;
    unsigned short nTracks;
    unsigned short division;
    ifstream file(filename, ios_base::in | ios_base::binary);

    if (!file) {
        file.close();
        throw FileException("Error de apertura de archivo");
    }

    if (isLoaded) {
        unloadTracks();
        isLoaded = false;
    }

    // Cabecera del archivo

    file.read(header, 4);

    if (memcmp(header, "MThd", 4) != 0) {
        file.close();
        throw FileException("Fichero incompatible. Se esperaba \"MThd\"");
    }

    file.read(reinterpret_cast<char*> (&length), 4);
    MIDIFILE_SWAP32(length);

    if (length != 6) {
        file.close();
        throw FileException("Fichero incompatible. Se esperaba longitud 6");
    }

    file.read(reinterpret_cast<char*> (&format), 2);
    MIDIFILE_SWAP32(format);

    if (format > 2) {
        file.close();
        throw FileException("Fichero incompatible. Formato de pistas inesperado");
    }

    file.read(reinterpret_cast<char*> (&nTracks), 2);
    MIDIFILE_SWAP32(nTracks);

    file.read(reinterpret_cast<char*> (&division), 2);
    MIDIFILE_SWAP32(division);

    if ((division & 0x8000) == 0) {
        divisionType = TicksPerBeat;
        ticksPerBeat = division;
    } else {
        divisionType = FramesPerSecond;
        framesPerSecond = (division & 0x7F00) >> 8;
        ticksPerFrame = (division & 0xFF);
    }

    // Pistas de la secuencia

    tracks.resize(nTracks);

    for (unsigned short i = 0; i < nTracks; i++)
        tracks[i] = new Track(file);

    isLoaded = true;
    file.close();
}

void Sequence::unloadTracks()
{
    for (unsigned int i = 0; i < tracks.size(); i++)
        delete tracks[i];
}
